/*
Copyright (C) 2011 by the PSVN Research Group, University of Alberta
*/

#include <stdlib.h>
#include <stdio.h>
#include <inttypes.h>
#include <dlfcn.h>
#include <assert.h>
#include "psvn_game_so.h"
#include "hida.hpp"
#include "timer.hpp"

using namespace std;

const bool PRINT_SOLUTIONS = false;

char* print_bytes(size_t bytes)
{
    static char str[128];
    if (bytes < 1024)
        sprintf(str, "%lub", bytes);
    else if (bytes < 1024*1024)
        sprintf(str, "%lukb", bytes / 1024);
    else if (bytes < 1024*1024*1024)
        sprintf(str, "%lumb", bytes / (1024*1024));
    else
        sprintf(str, "%.2lfgb", (double)bytes / (1024*1024*1024));
    return str;
}

int main(int argc, char** argv)
{
    if (argc < 2) {
        fprintf( stderr, "requires at least one shared game object\n" );
        exit( EXIT_FAILURE );
    }
    bool use_fringe = false;
    int clear = 1;
    string nameFile("-");
    for (int i = 1; i < argc; ++i) {
        if (!strcmp(argv[i], "--use-fringe")) {
            printf("Using fringe search.\n");
            use_fringe = true;
        } else if (!strcmp(argv[i], "--clear")) {
            sscanf(argv[i+1], " %d", &clear);
            if (clear < 1 && clear != -1) {
                fprintf(stderr, "Must be -1 or greater than 0.\n");
                exit(EXIT_FAILURE);
            }
            if (clear == -1)
                printf("Never clearing cache.\n");
            else
                printf("Clearing cache after %d problem%s.\n",
                       clear, (clear==1)?"":"s");
            ++i;
        } else {
            nameFile = string(argv[i]);
        }
    }
    vector<AbstractionDescription> desc 
        = AbstractionDescription::Load(nameFile);
    printf("Using %lu levels of abstraction.\n", desc.size() - 1);
    AbstHierarchy psvn(desc);
    HIDA<AbstHierarchy> hida(use_fringe, psvn);

    size_t total_expanded = 0;
    size_t total_wasted = 0;
    size_t total_stored = 0;
    vector<size_t> expanded(desc.size(), 0);
    char line[4096];
    total_expanded = 0;
    Timer total_timer;
    void* state = new_so_state(desc[0].m_game);
    for(size_t trials = 0;
        fgets(line, 4096, stdin) != NULL
            && read_so_state(line, state, desc[0].m_game) > 0;
        ++trials ) {
        
        printf( "problem %lu: ", trials + 1 );
        print_so_state( stdout, state, desc[0].m_game);
        printf( "\n" );

        if (clear != -1 && ((trials % clear) == 0)) {
            printf("<clearing cache>\n");
            hida.ClearCache();
        }

        Timer instance_timer;
        vector<const void*> solution;
        int d = hida.Solve(state, &solution);
        if (PRINT_SOLUTIONS) {
            if (solution.empty())
                printf("no solution\n");
            else {
                printf("d = %d\n", d);
                printf("solution (%d moves):\n", (int)solution.size() - 1);
                for (size_t i = 0; i < solution.size(); ++i) {
                    printf("  ");
                    print_so_state(stdout, solution[i], desc[0].m_game);
                    printf("\n");
                }
            }
        }
        printf("elapsed  %0.3lf\n", instance_timer.Elapsed());

        size_t total = 0;
        vector<size_t> data;

        total = 0;
        hida.NumSearches(data);
        printf("searches");
        for (size_t i = 0; i < data.size(); ++i) {
            printf(" %lu", data[i]);
            total += data[i];
        }
        printf(": %lu\n", total);

        hida.NumExpanded(data);
        total = 0;
        printf("expanded");
        for (size_t i = 0; i < data.size(); ++i) {
            printf(" %lu", data[i]);
            total += data[i];
            expanded[i] += data[i];
        }
        printf(": %lu\n", total);
        total_expanded += total;

        total = 0;
        hida.NumWasted(data);
        printf("wasted  ");
        for (size_t i = 0; i < data.size(); ++i) {
            printf(" %lu", data[i]);
            total += data[i];
        }
        printf(": %lu\n", total);
        total_wasted += total;

        hida.NumStored(data);
        total = 0;
        printf("stored  ");
        for (size_t i = 0; i < data.size(); ++i) {
            printf(" %lu", data[i]);
            total += data[i];
        }
        printf(": %lu\n", total);
        total_stored += total;

        hida.Memory(data);
        total = 0;
        printf("memory  ");
        for (size_t i = 0; i < data.size(); ++i) {
            printf(" %s", print_bytes(data[i]));
            total += data[i];
        }
        printf(": %s\n", print_bytes(total));
        
    }
    destroy_so_state(state);
    double elapsed = total_timer.Elapsed();
    printf("==================================\n");
    printf("pct.work ");
    for (size_t i = 0; i < expanded.size(); ++i)
        printf("%.1f%% ", expanded[i] * 100.0 / total_expanded);
    printf("\n");
    printf("expanded %lu (%.3gm) %.2fm/s\n", total_expanded,
           (total_expanded / 1000000.0),
           (total_expanded / 1000000.0) / elapsed);
    printf("wasted   %lu (%.3gm) %.2f%%\n", total_wasted,
           (total_wasted / 1000000.0),
           (total_wasted * 100.0) / total_expanded);
    printf("stored   %lu (%.3gm) %.2fm/s\n", total_stored,
           (total_stored / 1000000.0),
           (total_stored / 1000000.0) / elapsed);
    printf("elapsed  %.03lfs\n", elapsed);
    
    return EXIT_SUCCESS;
}
